package dslab.nameserver;

import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.util.Map;

public class NameserverRemote implements INameserverRemote {

    private final String componentId;
    private final boolean root;
    private final String root_id;
    private final Map<String, INameserverRemote> nameservers;
    private final Map<String, String> addresses;
    private final Registry registry;
    private final INameserverRemote root_stub;

    public NameserverRemote(String componentId, boolean root, String root_id,
                            Map<String, INameserverRemote> nameservers,
                            Map<String, String> addresses, Registry registry,
                            INameserverRemote root_stub) throws RemoteException {
        this.componentId = componentId;
        this.root = root;
        this.root_id = root_id;
        this.nameservers = nameservers;
        this.addresses = addresses;
        this.registry = registry;
        this.root_stub = root_stub;
    }

    @Override
    public void registerNameserver(String domain, INameserverRemote nameserver) throws RemoteException,
            AlreadyRegisteredException, InvalidDomainException {
        System.out.printf("@%s: registerNameserver called with domain %s%n", componentId, domain);
        // in case of root level nameserver
        if (!domain.contains(".")) {
            System.out.printf("@%s: adding root level nameserver with domain %s%n", componentId, domain);

            // do not allow re-registration of nameserver
            if (nameservers.containsKey(domain)) {
                System.err.printf(
                    "@%s: error: cannot register nameserver, \"%s\" is already registered%n",
                    componentId,
                    domain
                );
                throw new AlreadyRegisteredException(String.format(
                    "cannot register nameserver, \"%s\" is already registered",
                    domain
                ));
            }

            nameservers.put(domain, nameserver);
        // in case of nameserver that control a zone within that of another root level nameserver
        } else {
            String[] zones = domain.split("\\.");
            String rootLevelZone = zones[zones.length - 1];
            String zonesWithoutRoot = domain.substring(0, domain.length() - rootLevelZone.length() - 1);
            System.out.printf("@%s: contacting nameserver with domain %s%n", componentId, rootLevelZone);

            // attempt registering nameserver recursively
            INameserverRemote next = nameservers.get(rootLevelZone);
            if (next == null) {
                System.err.printf(
                    "@%s: error: cannot register nameserver, next nameserver hop \"%s\" not known%n",
                    componentId,
                    domain
                );
                throw new InvalidDomainException(String.format(
                    "cannot register nameserver, next nameserver hop \"%s\" not known",
                    zonesWithoutRoot
                ));
            }

            // make recursive call
            next.registerNameserver(zonesWithoutRoot, nameserver);
        }
    }

    @Override
    public void registerMailboxServer(String domain, String address) throws RemoteException,
            AlreadyRegisteredException, InvalidDomainException {
        System.out.printf("@%s: registerMailboxServer called with domain %s%n", componentId, domain);
        // in case of root level nameserver
        if (!domain.contains(".")) {
            System.out.printf("@%s: adding address %s to this nameserver with domain %s%n", componentId, address, domain);

            // do not allow re-registration of addresses
            if (addresses.containsKey(domain)) {
                System.err.printf(
                        "@%s: error: cannot register addresses, \"%s\" is already registered%n",
                        componentId,
                        address
                );
                throw new AlreadyRegisteredException(String.format(
                        "cannot register address, \"%s\" is already registered",
                        address
                ));
            }

            addresses.put(domain, address);
            // in case of nameserver that control a zone within that of another root level nameserver
        } else {
            String[] zones = domain.split("\\.");
            String rootLevelZone = zones[zones.length - 1];
            String zonesWithoutRoot = domain.substring(0, domain.length() - rootLevelZone.length() - 1);
            System.out.printf("@%s: contacting root level nameserver with domain %s%n", componentId, rootLevelZone);

            // attempt registering nameserver recursively
            INameserverRemote next = nameservers.get(rootLevelZone);
            if (next == null) {
                System.err.printf(
                        "@%s: error: cannot register mailbox server, next nameserver hop \"%s\" not known%n",
                        componentId,
                        domain
                );
                throw new InvalidDomainException(String.format(
                        "cannot register mailbox server, next nameserver hop \"%s\" not known",
                        zonesWithoutRoot
                ));
            }

            // make recursive call
            next.registerMailboxServer(zonesWithoutRoot, address);
        }
    }

    @Override
    public INameserverRemote getNameserver(String zone) throws RemoteException {
        System.out.printf("@%s: Nameserver for %s requested", componentId, zone);
        return nameservers.get(zone);
    }

    @Override
    public String lookup(String username) throws RemoteException {
        System.out.printf("@%s: Lookup of %s requested", componentId, username);
        return addresses.get(username);
    }

    public String lookupDomain(String domainName) throws RemoteException {
        if (!root) {
            if (root_stub != null) {
                return root_stub.lookupDomain(domainName);
            } else {
                return null;
            }
        } else {
            String[] zones = domainName.split("\\.");
            INameserverRemote nameserver;
            nameserver = this;
            // query nameservers until the last zones nameserver is reached
            for (int i = zones.length - 1; i > 0; i--) {
                System.out.println(zones[i]);
                System.out.println(nameserver);
                nameserver = nameserver.getNameserver(zones[i]);
                if (nameserver == null) {
                    return null;
                }
            }
            return nameserver.lookup(zones[0]);
        }
    }
}
